import uuid
import tornado.gen
import tornado.web
import tornado.httpclient
from concurrent.futures import ThreadPoolExecutor
from tornado.concurrent import run_on_executor

from bot.enterprise_wechat_gpt.qw_bot import *
from bot.enterprise_wechat_gpt.util.user_util import *
from bot.enterprise_wechat_gpt.util.fixed_speech import weapp_speech, link_speech
from bot.enterprise_wechat_gpt.util.transfer_util import *
from bot.enterprise_wechat_gpt.util.id_distributor import distribute, save_A_or_B
from bot.enterprise_wechat_gpt.util.conversation_history_util import conversation_history_util, parse_raw_list, get_agent_last_msg
from bot.enterprise_wechat_gpt.util.monitor_util import monitor_timeout
from bot.enterprise_wechat_gpt.util.msg_buffer_util import msg_buffer_util
from bot.enterprise_wechat_gpt.util.constant.loan_url import Loan_URL
from bot.enterprise_wechat_gpt.util.constant.hidden_character import *
from bot.enterprise_wechat_gpt.util.postgre_sql_util import *


class QueryHandler(tornado.web.RequestHandler):
    # 启动多个线程
    executor = ThreadPoolExecutor(9)

    @tornado.gen.coroutine
    def get(self):
        user_id = self.get_argument('userId', None)
        msg = self.get_argument('msg', '')
        msg_type = self.get_argument('msgType', 'text')
        debug = self.get_argument('debug', 'false')
        output = yield self.handle_get(user_id, msg, msg_type, debug)
        self.write(json.dumps(output))

    @run_on_executor
    def handle_get(self, user_id, msg, msg_type, debug):
        output = {
            'msgtype': 'text',
            'text': {
                'content': conversation_history_util.welcome_msg
            }
        }
        if user_id is None:
            logging.info("没有用户Id, 直接返回空字符串")
            output['text']['content'] = ''
            return output

        if '开始测试' == msg:
            logging.info("-" * 20 + user_id + '对话开始' + "-" * 20)
            conversation_history_util.flush(user_id)
            return output
        elif '再见' in msg:
            logging.info("-" * 20 + user_id + '对话结束' + "-" * 20)
            conversation_history_util.flush(user_id)
            output['text']['content'] = '好的，再见~'
            return output

        if get_transfer(user_id):
            logging.info(f"userId:[{user_id}] |pre 用户说: {msg} 已经转人, 不再处理")
            output['text']['content'] = '已转人工'
            output['transfer'] = True
            return output
        dialogue = conversation_history_util.add_dialogue(user_id, USER, msg, msg_type)
        logging.info(f"userId:[{user_id}] dialogue process | pre 用户说: {msg}")

        thread_id = threading.current_thread().ident
        # 如果缓冲队列里面有消息堆积，就停止之前的协程，然后重新处理"堆积消息+新消息"
        msg = msg_buffer_util.join_buffer_msg(msg, user_id)
        # 如果没有消息堆积，那么就把消息放到缓冲队列
        msg_buffer_util.push_buffer(user_id, msg, thread_id)
        local_data.wx_id = user_id
        res_total = total_process(dialogue, msg, user_id)

        # 处理完成检查一下缓冲队列，如果自己处理的消息和缓冲队列里面拿出来的消息不一致，就不返回；否则把缓冲队列清空
        if msg_buffer_util.judge_stop_handle(msg, user_id, thread_id):
            output['text']['content'] = ''
            return output

        replay = res_total.get("最终话术", "Get_None_Utter!")
        logging.info(f"userId:[{user_id}] dialogue process | post AI回复: {replay}")
        conversation_history_util.add_dialogue(user_id, ASSISTANT, res_total)

        debug_info = json.dumps(res_total, ensure_ascii=False, indent=2)

        if debug == 'true':
            replay += '\n\n===> ' + debug_info

        output['text']['content'] = replay
        if res_total.get('是否转人', False):
            output['transfer'] = True
        return output

    @tornado.gen.coroutine
    def post(self):
        body = self.request.body.decode("utf-8")
        body = body.replace(" ", "")
        msg_list = json.loads(body)
        customer_id = self.get_argument('customerId', None)
        logging.info(f"customerId:{customer_id} 收到消息 >>>>>>>>>>>>>>>:{json.dumps(msg_list, ensure_ascii=False)}")
        start_time = time.time()
        output = yield self.handle_post(msg_list, customer_id)
        output = join_hidden_char_for_output(output)
        dumps = json.dumps(output)
        logging.info(f"customerId:{customer_id} 返回消息 >>>>>>>>>>>>>>>:{json.dumps(output, ensure_ascii=False)}")
        total_handle_time = int(time.time() - start_time)
        total_processing_time = output.get("total_processing_time", total_handle_time)
        end_2_end_time = output.get("end_2_end_time", total_handle_time)
        processing_timeout = total_handle_time - total_processing_time
        front_handle_time = end_2_end_time - total_handle_time
        logging.info(f"customerId:{customer_id} 请求总耗时:{total_handle_time}s "
                     f"请求被延迟处理:{processing_timeout}s 前端耗时:{front_handle_time}s")
        monitor_timeout(processing_timeout, front_handle_time)
        self.write(dumps)

    @run_on_executor
    def handle_post(self, msg_list, customer_id):
        output = {
            'msgtype': 'text',
            'text': {'content': ''}
        }
        if not msg_list:
            logging.warning(f"customerId:{customer_id} 消息为空, 直接返回")
            return output
        # 提取最后一句用户的话
        last_msg_body = msg_list[0]
        wx_id = last_msg_body['externalUserId']
        uid = get_uid(customer_id, last_msg_body)
        last_msg_time = get_last_msg_time(last_msg_body)

        mark_new_user(uid)
        local_data.wx_id = wx_id
        dialogue_list = parse_raw_list(uid, msg_list)
        logging.info(f"userId:[{uid}] 完整的消息:{dialogue_list}")
        msg_types = conversation_history_util.get_msg_types(last_msg_time, dialogue_list, uid)
        user_say = conversation_history_util.get_user_say(last_msg_time, dialogue_list, uid)
        # 统计回复率
        statistic_answer_rate(get_agent_last_msg(dialogue_list), user_say, uid, wx_id)

        # <editor-fold desc="获取AI回复前判断是否转人">
        A = distribute(wx_id, 10)
        save_A_or_B(last_msg_body['userId'], customer_id, wx_id, 'A' if A else 'B')
        if A:
            logging.info(f"userId:[{uid}] wxId:[{wx_id}] 分流测试, A组, 不调用大模型")
            output['transfer'] = True
            return output
        else:
            logging.info(f"userId:[{uid}] wxId:[{wx_id}] 分流测试, B组, 调用大模型")

        if user_has_big_balance(wx_id):
            logging.info(f"userId:[{uid}] wxId:[{wx_id}] dialogue process | post 发生转人操作, 转人原因: 金条剩余额额度_实时 大于20000, 且可用信 不调用大模型")
            output['transfer'] = True
            output['text']['content'] = '发生转人操作，转人原因: 大额客户'
            compute_transfer_rate(output)
            return output
        # </editor-fold>

        try:
            if get_transfer(uid):
                logging.info(f"userId:[{uid}] dialogue process | post 用户已经转人, 不再处理")
                output['text']['content'] = '已转人工'
                output['transfer'] = True
                return output

            # TODO 应该先判断消息类型，再判断消息内容，不然会出现没有处理过的消息类型，返回空，rpa一直请求的情况
            if user_say == '' and last_msg_body['msgtype'] == 'revoke':
                logging.warning(f"userId:[{uid}] dialogue process | post 用户消息为空, 且最后的消息类型是revoke，不调用大模型, 直接返回固定消息")
                output['text']['content'] = '[玫瑰]您好，目前是有遇到什么问题吗？看下这边能否帮助到您呢~'
                return output
            if user_say == '':
                # 甲方消息没发出去，rpa重试直接返回之前返回的消息
                cache_response = conversation_history_util.get_ai_response(uid)
                if cache_response is not None:
                    output = cache_response
                    logging.warning(f"userId:[{uid}] dialogue process | post 用户消息为空, 不调用大模型, 返回缓存:{output[output['msgtype']]['content']}")
                else:
                    logging.warning(f"userId:[{uid}] dialogue process | post 用户消息为空, 不调用大模型, 没有缓存, 返回空")
                return output
            output = get_output(uid, user_say, msg_types, dialogue_list)
            if output['text']['content'] != '':
                conversation_history_util.cache_ai_response(uid, output)
                conversation_history_util.set_last_msg_time(uid, last_msg_time)
                # 统计转接率
                compute_transfer_rate(output)
            return output
        except Exception as e:
            logging.error(f"userId:[{uid}] dialogue process | post 大模型调用异常: {e}\n{traceback.format_exc()}")
            output['text']['content'] = ''
            return output
        finally:
            second = (int(time.time()) - int(last_msg_time))
            output['end_2_end_time'] = second
            duration = second / 60
            if duration > 3 and time.localtime().tm_hour > 9:
                logging.warning(f"userId:[{uid}] 端到端耗时:{duration}分钟 回复时长超过五分钟")
            else:
                logging.info(f"userId:[{uid}] 端到端耗时:{duration}分钟")


def get_output(user_id, msg, msg_types, dialogue: list):
    output = {
        'msgtype': 'text',
        'text': {'content': ''}
    }
    logging.info(f"userId:[{user_id}] dialogue process | pre 用户说: {msg}")
    # 新用户直接转人
    if msg == "我通过了你的联系人验证请求，现在我们可以开始聊天了":
        # output['text']['content'] = conversation_history_util.welcome_msg
        logging.info(f"userId:[{user_id}] dialogue process | post 新用户, 直接转人")
        output['text']['content'] = '发生转人操作，转人原因：新用户'
        output['transfer'] = True
        return output
    conversation_history_util.store_conversation(user_id, USER, msg)
    # dialogue = add_transfer_msg(dialogue, user_id)
    # 群发激活直接转人
    agent_last_msg = get_agent_last_msg(dialogue)
    if msg == '1' and len(dialogue) >= 2 and "【1】" in agent_last_msg:
        logging.info(f"userId:[{user_id}] dialogue process | post 群发激活, 用户回1, 直接转人. 群发话术: [{agent_last_msg}]")
        output['text']['content'] = '发生转人操作，转人原因：群发激活, 用户回1'
        output['transfer'] = True
        return output

    thread_id = threading.current_thread().ident
    # 如果缓冲队列里面有消息堆积，就停止之前的协程，然后重新处理"堆积消息+新消息"
    msg = msg_buffer_util.join_buffer_msg_for_post(msg, user_id)
    # 如果没有消息堆积，那么就把消息放到缓冲队列
    msg_buffer_util.push_buffer(user_id, msg, thread_id)

    all_text = all(msg_type == 'text' for msg_type in msg_types)
    if all_text is False:
        # 去除文本消息，如果剩下的消息类型都是小程序消息，那么就直接返回固定话术
        filtered_msg_types = [msg_type for msg_type in msg_types if msg_type != 'text']
        if all(msg_type == 'weapp' for msg_type in filtered_msg_types):
            output['text']['content'] = next(weapp_speech)
            logging.info(f"userId:[{user_id}] dialogue process | post 小程序消息, 直接返回固定话术")
            msg_buffer_util.pop_buffer(user_id)
            return output
        # 去除文本消息和小程序消息，如果剩下的消息类型都是链接消息，那么就直接返回固定话术
        filtered_msg_types = [msg_type for msg_type in filtered_msg_types if msg_type != 'weapp']
        if all(msg_type == 'link' for msg_type in filtered_msg_types):
            output['text']['content'] = next(link_speech)
            logging.info(f"userId:[{user_id}] dialogue process | post 链接消息, 直接返回固定话术")
            msg_buffer_util.pop_buffer(user_id)
            return output
        output['text']['content'] = '发生转人操作，转人原因：消息类型不是文本'
        output['transfer'] = True
        logging.info(f"userId:[{user_id}] dialogue process | post 发生转人操作 转人原因: 消息类型含有非文本类型, 转人, 弹出缓冲区的消息 msg_types:{msg_types}")
        msg_buffer_util.pop_buffer(user_id)
        return output

    try:
        start_time = time.time()
        res_total = total_process(dialogue, msg, user_id)
        total_processing_time = int(time.time() - start_time)
        output['total_processing_time'] = total_processing_time
        logging.info(f"userId:[{user_id}] AI处理总耗时：{total_processing_time}s ")
    except Exception as e:
        msg_buffer_util.pop_buffer(user_id)
        logging.warning(f"userId:[{user_id}] 处理过程中发生异常, 弹出缓冲区的消息 {e}\n{traceback.format_exc()}")
        raise

    # 处理完成检查一下缓冲队列，如果自己处理的消息和缓冲队列里面拿出来的消息不一致，就不返回；否则把缓冲队列清空
    if msg_buffer_util.judge_stop_handle(msg, user_id, thread_id):
        # 防止已经有一个线程已经判断转人了，然后该线程不知道，还返回不转人
        output['transfer'] = get_transfer(user_id)
        return output

    replay = res_total.get("最终话术", "Get_None_Utter!")
    # logging.info(f"userId:[{user_id}] return type:{type(replay)}")
    if type(replay) is not str:
        # 这里也是因为该线程被其他处理消息的线程停止了，然后返回的是coroutine类型，所有这里直接返回空字符串
        logging.warning(f"userId:[{user_id}] dialogue process | post 话术生成异常, 不是字符串类型, 返回空字符串, 原始:{replay}")
        return output
    logging.info(f"userId:[{user_id}] dialogue process | post AI回复: {replay}")
    conversation_history_util.store_conversation(user_id, ASSISTANT, res_total)

    output['text']['content'] = replay
    if res_total.get('是否转人', False):
        output['transfer'] = True
    return output


def statistic_answer_rate(agent_last_msg, user_say, user_id, wx_id):
    date = datetime.datetime.now().strftime("%m-%d")
    if jr_hidden_character.strip() in agent_last_msg:
        user_pool = Loan_URL.微助手.name
        tair_util.incr(f"QW:QFJH:{user_pool}:{date}")
        update_activate_data(wx_id, user_say, agent_last_msg)
    elif jt_hidden_character.strip() in agent_last_msg:
        user_pool = Loan_URL.金条.name
        tair_util.incr(f"QW:QFJH:{user_pool}:{date}")
        update_activate_data(wx_id, user_say, agent_last_msg)
    elif "【1】" in agent_last_msg:
        user_pool = "甲方群发"
        tair_util.incr(f"QW:QFJH:PartA:{date}")
    else:
        user_pool = "非群发"
    logging.info(
        f"userId:[{user_id}] wx_id:{wx_id} statistics 群发激活 回复率统计 用户池:{user_pool} 群发话术: [{agent_last_msg}]")


def get_uid(customer_id, last_msg_body):
    if customer_id is None or customer_id == 'None' or customer_id == '':
        customer_id = last_msg_body['externalUserId']
        logging.warning(f"userId:[{customer_id}] customer_id为空, 把externalUserId设置为customer_id")
    uid = last_msg_body['userId'] + ':' + customer_id
    return uid


def get_last_msg_time(last_msg_body):
    msgtime_ = last_msg_body['msgtime']
    last_msg_time = msgtime_ if len(str(msgtime_)) == 10 else int(msgtime_ / 1000)
    return last_msg_time


# def add_transfer_msg(dialogue, user_id):
#     conversation_history = conversation_history_util.get_conversation_history(user_id)
#     dialogue.extend(conversation_history)
#     dialogue = sorted(dialogue, key=lambda x: x['msgtime'] if len(str(x['msgtime'])) == 10 else x['msgtime'] / 1000)
#     return dialogue
